﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using SlimDX.Direct3D10;
using SlimDX.DXGI;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Restitue un <see cref="Geometry3D10"/>. 
    /// </summary>
    public sealed class GeometryModel3D10 : RenderModel3D10
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Geometry"/>.
        /// </summary>
        public static readonly DependencyProperty GeometryProperty =
            DependencyProperty.Register("Geometry", typeof(Geometry3D10), typeof(GeometryModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Geometry_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Materials"/>.
        /// </summary>
        public static readonly DependencyProperty MaterialsProperty = 
            DependencyProperty.Register("Materials", typeof(MaterialCollection), typeof(GeometryModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Materials_PropertyChanged)));
        #endregion

        #region Dependency Properties Callbacks
        private static void Geometry_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as GeometryModel3D10).OnGeometryChanged(e);
        }

        private static void Materials_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as GeometryModel3D10).OnMaterialsChanged(e);
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="GeometryModel3D10"/>
        /// </summary>
        public GeometryModel3D10()
            : base()
        {
            this.Materials = new MaterialCollection();
        }

        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="GeometryModel3D10"/> avec une géometry 3-D.
        /// </summary>
        /// <param name="geometry"><see cref="Geometry3D10"/> à appliquer.</param>
        public GeometryModel3D10(Geometry3D10 geometry)
            : base()
        {
            this.Geometry = geometry;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Liste des matériaux à appliquer
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227")]
        public MaterialCollection Materials
        {
            get { return (MaterialCollection)GetValue(MaterialsProperty); }
            set { SetValue(MaterialsProperty, value); }
        }

        /// <summary>
        /// Geométrie de l'objet 3-D
        /// </summary>
        public Geometry3D10 Geometry
        {
            get { return (Geometry3D10)GetValue(GeometryProperty); }
            set { SetValue(GeometryProperty, value); }
        }
        #endregion

        #region Private Members
        private void OnGeometryChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            InvalidateInputLayout();
        }

        private void OnMaterialsChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            this.Materials.SetEffect(this.Effect);
        }
        #endregion

        #region Model3D Overrides
        /// <summary>
        /// Appelée lors du changement de l'effet
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected override void OnEffectChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnEffectChanged(e);
            if (this.Materials != null) this.Materials.SetEffect(this.Effect);
        }

        /// <summary>
        /// En cas de substitution dans une classe dérivée, participe au dessin des objets dans le pipeline DirectX10
        /// </summary>
        /// <param name="context">Contexte de dessin.</param>
        /// <exception cref="ArgumentNullException">Soulevée si le contexte est null.</exception>
        protected override void OnDraw(DrawingContext context)
        {
            if (object.ReferenceEquals(context, null))
                throw new ArgumentNullException("context");

            base.OnDraw(context);
            foreach (IDrawable material in this.Materials) material.Draw(context);

            MeshGeometry3D10 meshGeometry = Geometry as MeshGeometry3D10;
            if (meshGeometry != null)
                context.DrawIndexed(this, PrimitiveTopology.TriangleList, meshGeometry.TriangleIndices);
        }
        #endregion

        #region RenderModel3D10 Implementation
        /// <summary>
        /// Crée le <see cref="InputLayout"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override InputLayout CreateInputLayoutCore()
        {
            return this.CurrentEffectTechnique == null ? null : this.CurrentEffectTechnique.CreateInputLayout(Geometry.GetInputElements());
        }

        /// <summary>
        /// Crée le <see cref="VertexBufferBinding"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override SlimDX.Direct3D10.VertexBufferBinding[] CreateVertexBufferBindingCore()
        {
            return Geometry.GetVertexBufferBinding();
        }
        #endregion

        #region Freezable Implementation
        /// <summary>
        /// Une fois implémentée dans une classe dérivée, initialise une nouvelle instance de la classe dérivée <see cref="Freezable"/>. 
        /// </summary>
        /// <returns>Nouvelle instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new GeometryModel3D10();
        }
        #endregion
    }
}
